﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace StephenEssential
{

    public static class Tools
    {
        public static Point ToPoint(this int num, int width)
        {
            return new Point(num%width, num/width);
        }
        public static int Sum<T>(this IEnumerable<T> list, Func<T, int, int> sumFunc)
        {
            return list.Select(sumFunc).Sum();
        }
        public static IList<TOut> Select<T, TOut>(this IList<T> list, Func<T, TOut> func)
        {
            var outcome = new List<TOut>(list.Count);
// ReSharper disable once LoopCanBeConvertedToQuery
            foreach (var t in list)
                outcome.Add(func(t));
            return outcome;
        }
        public static void Loop(this int time, Action action)
        {
            for (var i = 0; i < time; i++)
                action();
        }
        public static void Loop(int start, int end, int stride, Action<int> action)
        {
            for (var i = start; i < end + 1; i += stride)
                action(i);
        }
        public static void PLoop(this int time, Action action)
        {
            Parallel.For(0, time, i => action());
        }

        /// <summary>
        /// Loop with a parameter i from 0 to time-1
        /// </summary>
        /// <param name="time"></param>
        /// <param name="action"></param>
        public static void Loop(this int time, Action<int> action)
        {
            for (var i = 0; i < time; i++)
                action(i);
        }
         
        
        public static T Iterate<T>(this T initValue, int time, Func<T, T> func)
        {
            if (time < 1) throw new ArgumentException();
            var n = initValue;
            for (var i = 0; i < time; i++)
                n = func(n);
            return n;
        }

        public static readonly List<Point> EightNeighbour = new Bitmap<Point>(3, 3, (x, y) => new Point(x - 1, y - 1)).ToList();

        
        public static List<T> Append<T>(this List<T> ori, IEnumerable<T> list)
        {
            ori.AddRange(list);
            return ori;
        }
        public static void PrintDoubles(this Bitmap<double> oMap)
        {
            var list = new List<double>();
            oMap.ForEach(d =>
                {
                    if (d > 0) list.Add(d);
                });
            using (var streamWriter = File.CreateText("DoubleMap.txt"))
            {
                foreach (var t in list) {
                    streamWriter.WriteLine("{0}   ", t);
                }
            }
        }
        
        public static TOut Pipe<TIn, TOut>(this TIn ori, Func<TIn, TOut> func)
        {
            return func(ori);
        }
        public static T Pipe<T>(this T ori, Action<T> action)
        {
            action(ori);
            return ori;
        }
        

        
        public static void ForEach<T>(this IList<T> list, Action<T, int> action)
        {
            for (var i = 0; i < list.Count; i++)
                action(list[i], i);
        }
        public static void ForEach<T>(this IEnumerable<T> list, Action<T> action)
        {
            foreach (var t in list)
                action(t);
        }

        public static double Abs(this double num)
        {
            return num >= 0 ? num : -num;
        }

        
        
        public struct IntervalIntersection
        {
            public readonly int StartPointRelIntervalA, StartPointRelIntervalB, BlockWidth;
            public readonly bool Validity;
            private static readonly IntervalIntersection InvalidArgu = new IntervalIntersection(false);
            private static readonly Func<int, int, int> FindMin = (a, b) => a < b ? a : b;

            private IntervalIntersection(bool validity) : this (0, 0, 0)
            {
                Validity = validity;
            }

            private IntervalIntersection(int startPointRelIntervalA, int startPointRelIntervalB, int blockWidth)
            {
                StartPointRelIntervalA = startPointRelIntervalA;
                StartPointRelIntervalB = startPointRelIntervalB;
                BlockWidth = blockWidth;
                Validity = true;
            }
            public static IntervalIntersection CalcIntersection(int intervalAZeroPointRelIntervalBCoord, int intervalAWidth, int intervalBWidth)
            {
                if (intervalAZeroPointRelIntervalBCoord < 0)
                    return intervalAZeroPointRelIntervalBCoord + intervalAWidth <= 0
                               ? InvalidArgu
                               : new IntervalIntersection(0 - intervalAZeroPointRelIntervalBCoord, 0,
                                                  FindMin(intervalBWidth, intervalAZeroPointRelIntervalBCoord + intervalAWidth));
                return intervalAZeroPointRelIntervalBCoord >= intervalBWidth
                           ? InvalidArgu
                           : new IntervalIntersection(0, intervalAZeroPointRelIntervalBCoord,
                                              FindMin(intervalAZeroPointRelIntervalBCoord + intervalAWidth, intervalBWidth) - intervalAZeroPointRelIntervalBCoord);
            }
        }
        

        
        
        public static void ForEach<T>(this T[,] block, Action<T> action)
        {
            for (var i = 0; i < block.GetLength(0); i++)
                for (var j = 0; j < block.GetLength(1); j++)
                    action(block[i, j]);
        }
        public static void ForEach<T>(this T[,] block, Action<T, int, int> action)
        {
            for (var i = 0; i < block.GetLength(0); i++)
                for (var j = 0; j < block.GetLength(1); j++)
                    action(block[i, j], j, i);
        }

        
        public static void TextWriter(string label, Action<StreamWriter> action)
        {
            using (
                var writer =
                    new StreamWriter(String.Format("{0}     {1}", label, DateTime.Now.ToString("hh.mm.ss fff")) + ".txt")
                )
                action(writer);
        }

        /// <summary>
        /// An Permutation of [0..(length-1)]
        /// </summary>
        /// <param name="length"></param>
        /// <returns></returns>
        public static int[] RandomPermutation(int length)
        {
            var random = new Random();
            var arr = new List<Point>(length);
            length.Loop(i => arr.Add(new Point(i, random.Next())));
            return arr.OrderBy(p => p.Y).Select(p => p.X).ToArray();
        }

        
        /// <summary>
        /// Generate an array from(include) begin to(include) end, with a default stride 1
        /// </summary>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        /// <param name="stride"></param>
        /// <returns></returns>
        public static List<int> Range(int begin, int end, int stride = 1)
        {
            if (begin > end || stride <= 0) throw new ArgumentException();
            var list = new List<int>((end - begin + 1).Pipe(valley => valley%stride == 0 ? valley/stride : valley/stride + 1));
            for (var i = begin; i < end + 1; i += stride)
                list.Add(i);
            return list;
        }

        public static void Save(this string str, string saveName)
        {
            using (var writer = new StreamWriter(saveName))
                writer.Write(str);
        }

        public static int ToInt(this bool b)
        {
            return b ? 1 : 0;
        }

        public static IEnumerable<TOut> DecareProduct<TIn1, TIn2, TOut>(
            IEnumerable<TIn1> seq1,
            IEnumerable<TIn2> seq2,
            Func<TIn1, TIn2, TOut> combinator,
            Func<TIn1, TIn2, bool> predicate)
        {
            return 
                from in1 in seq1 
                from in2 in seq2 
                where predicate(in1, in2) 
                select combinator(in1, in2);
        }


        public static IEnumerable<TOut> DecareProduct<TIn1, TIn2, TOut>(
            IEnumerable<TIn1> seq1,
            IEnumerable<TIn2> seq2,
            Func<TIn1, TIn2, TOut> combinator)
        {
            return
                from in1 in seq1
                from in2 in seq2
                select combinator(in1, in2);
        }


        public static string AddTimeStamp(this string oStr, string fileType)
        {
            var suffix = DateTime.Now.ToString("           dd MMM, H.mm.ss fff",
                CultureInfo.CreateSpecificCulture("en-us"));
            return oStr + suffix + '.' +
                   (fileType.StartsWith(".") ? fileType.Substring(1, fileType.Length - 1) : fileType);
        }

        public static T[] NewArray<T>(int count, Func<int, T> func)
        {
            var arr = new T[count];
            for (var i = 0; i < count; i++)
                arr[i] = func(i);
            return arr;
        }

        public static List<Point> HollowNeiborhoods(this Point center, int radio)
        {
            if (radio <= 0) throw new ArgumentException();
            var resultList = new List<Point>((4*radio*(radio+1)));
            for (var i = -radio; i < radio + 1; i++)
                for (var j = -radio; j < radio + 1; j++)
                {
                    if (i == 0 & j == 0) continue;
                    resultList.Add(center + new Point(i, j));
                }
            return resultList;
        }

        public static void Discard<T>(this T ori) { }
        


        
    }
}
